package com.redstar.HappyRefresh.schedul;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.redstar.HappyRefresh.constant.AuditStatus;
import com.redstar.HappyRefresh.constant.RedisConstant;
import com.redstar.HappyRefresh.mapper.VideoMapper;
import com.redstar.HappyRefresh.pojo.Video;
import com.redstar.HappyRefresh.service.SettingService;
import com.redstar.HappyRefresh.service.VideoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import com.redstar.HappyRefresh.pojo.HotVideo;

import java.util.*;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class HotRank { // 热度排行榜

    @Autowired
    private VideoService videoService; // 视频
    @Autowired
    private VideoMapper videoMapper;
    @Autowired
    private RedisTemplate redisTemplate; // redis模板类
    @Autowired
    private SettingService settingService; // 设置

    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    ObjectMapper om = new ObjectMapper();

    {
        jackson2JsonRedisSerializer.setObjectMapper(om);
    }

    /**
     * 热度排行榜
     */
    @Scheduled(cron = "0 0 */1 * * ?") // 定时任务，过1个小时执行1次
    public void hotRank() {
        // 控制排行榜中视频的数量为10，优先队列按照热度从小到大排序
        TopK topK = new TopK(10, new PriorityQueue<>(10, Comparator.comparing(HotVideo::getHot)));
        long limit = 1000; // 每次拿1000条数据
        long id = 0;
        // select id, share_count, history_count, star_count, favorites_count, gmt_created, title from video where audit_status = 0 and open = 0 limit 1000
        List<Video> videos = videoService.list(new LambdaQueryWrapper<Video>()
                .select(Video::getId, Video::getShareCount, Video::getHistoryCount, Video::getStartCount, Video::getFavoritesCount,
                        Video::getGmtCreated, Video::getTitle).gt(Video::getId, id)
                .eq(Video::getAuditStatus, AuditStatus.SUCCESS).eq(Video::getOpen, 0).last("limit " + limit));
        while (!ObjectUtils.isEmpty(videos)) {
            for (Video video : videos) {
                // 分享数
                Long shareCount = video.getShareCount();
                // 浏览数
                Double historyCount = video.getHistoryCount() * 0.8;
                // 点赞数
                Long startCount = video.getStartCount();
                // 收藏数
                Double favoritesCount = video.getFavoritesCount() * 1.5;
                Date date = new Date();
                // 发布时间
                long t = date.getTime() - video.getGmtCreated().getTime();
                // 随机一个6位小数，用于去重，值较小，对热度的计算影响较小
                double v = weightRandom();
                // 计算当前视频的热度，热度始终大于0
                double hot = hot(shareCount + historyCount + startCount + favoritesCount + v, TimeUnit.MILLISECONDS.toDays(t));
                HotVideo hotVideo = new HotVideo(hot, video.getId(), video.getTitle());
                // 将当前视频封装成HotVideo对象，尝试放入排行榜中，热度小的会被替换
                topK.add(hotVideo);
            }
            id = videos.get(videos.size() - 1).getId(); // videos集合中最后一个视频
            // id是有序的，往后查询1000条数据，select * from id > #{id} limit 1000
            videos = videoService.list(new LambdaQueryWrapper<Video>().gt(Video::getId, id).last("limit " + limit));
        }
        // 删除热门排行榜中旧的数据
        redisTemplate.opsForZSet().removeRangeByScore(RedisConstant.HOT_RANK, 0, -1);
        // 将热度排行榜中的数据取出来
        List<HotVideo> hotVideos = topK.get();
        redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            for (HotVideo hotVideo : hotVideos) {
                // 将排行榜中的视频放入redis
                Double hot = hotVideo.getHot();
                try {
                    hotVideo.setHot(null);
                    // 不这样写会报错，序列化问题，将热门视频放入热门排行榜，key为hot:rank
                    connection.zAdd(RedisConstant.HOT_RANK.getBytes(), hot, jackson2JsonRedisSerializer.serialize(om.writeValueAsString(hotVideo)));
                } catch (JsonProcessingException e) {
                    log.error(e.getMessage(), e);
                }
            }
            return null;
        });
    }

    // 热门视频，没有热度排行榜实时且重要
    @Scheduled(cron = "0 0 */3 * * ?") // 过3个小时执行1次
    public void hotVideo() { // 分片查询3天内的视频
        int limit = 1000;
        long id = 1;
        // 获取id大于1的3天内发布的前1000条视频
        List<Video> videos = videoMapper.selectNDaysAgeVideo(id, 3, limit);
        // 获取sys_setting表中的规定热门视频的最小热度
        Double hotLimit = settingService.list(new LambdaQueryWrapper<>()).get(0).getHotLimit();
        Calendar calendar = Calendar.getInstance(); // 日历类
        int today = calendar.get(Calendar.DATE);
        while (!ObjectUtils.isEmpty(videos)) {
            // 封装热门视频id的集合
            ArrayList<Long> hotVideos = new ArrayList<>();
            for (Video video : videos) {
                // 分享数
                Long shareCount = video.getShareCount();
                // 浏览数
                Double historyCount = video.getHistoryCount() * 0.8;
                // 点赞数
                Long startCount = video.getStartCount();
                // 收藏数
                Double favoritesCount = video.getFavoritesCount() * 1.5;
                Date date = new Date();
                long t = date.getTime() - video.getGmtCreated().getTime();
                // 计算当前视频的热度
                double hot = hot(shareCount + historyCount + startCount + favoritesCount, TimeUnit.MILLISECONDS.toDays(t));
                // 大于X热度说明是热门视频，X是系统设置的门视频热度限制
                if (hot > hotLimit) {
                    hotVideos.add(video.getId());
                }
            }
            // RedisConstant.HOT_VIDEO + 今日日期 作为key
            if (!ObjectUtils.isEmpty(hotVideos)) {
                String key = RedisConstant.HOT_VIDEO + today; // key为hot:video:${today}
                // 将热门视频放入redis中，过期时间为3天
                redisTemplate.opsForSet().add(key, hotVideos.toArray(new Object[0]));
                redisTemplate.expire(key, 3, TimeUnit.DAYS);
            }
            // 最后一条视频的id，也就是最新发布的视频的id
            id = videos.get(videos.size() - 1).getId();
            // 获取3天内发布的之后的1000条视频
            videos = videoMapper.selectNDaysAgeVideo(id, 3, limit);
        }
    }

    public static double hot(double weight, double t) {
        // e的(-0.011 * t)次幂
        return weight * Math.exp(-0.011 * t);
    }

    public double weightRandom() {
        int i = (int) ((Math.random() * 9 + 1) * 100000); // 例：0.5 * 9 + 1 = 5.5 * 100000 = 550000 / 1000000 = 0.550000
        return i / 1000000.0;
    }

}
